using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.RateLimiting;
using HamsterDocs.Domains.Users;
using HamsterDocs.EntityFrameworkCore;
using HamsterDocs.Hosting.Extensions;
using HamsterDocs.Hosting.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileProviders;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

// OpenAPI
builder.Services.AddOpenApi();

// EF Core DbContext
builder.Services.UseDbContext(builder.Configuration);

 // Memory cache and HttpContext accessor for captcha, etc.
 builder.Services.AddMemoryCache();
 builder.Services.AddHttpContextAccessor();
 builder.Services.AddHttpClient();
 builder.Services.AddSingleton<ImageService>();
builder.Services.AddTransient<AuthService>();
builder.Services.AddTransient<CaptchaService>();
builder.Services.AddTransient<UsersService>();
builder.Services.AddTransient<DocsService>();

// JWT configuration
var jwtSection = builder.Configuration.GetSection("Jwt");
var issuer = jwtSection["Issuer"] ?? "hamsterdocs";
var audience = jwtSection["Audience"] ?? "hamsterdocs-web";
var expireMinutes = int.TryParse(jwtSection["ExpireMinutes"], out var em) ? em : 120;
var secret = jwtSection["Secret"];
if (string.IsNullOrWhiteSpace(secret))
{
    // Development fallback secret
    secret = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
}
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidateLifetime = true,
            ValidIssuer = issuer,
            ValidAudience = audience,
            IssuerSigningKey = signingKey,
            ClockSkew = TimeSpan.FromMinutes(1)
        };
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Admin", policy => policy.RequireClaim("role", "Admin"));
    options.AddPolicy("User", policy => policy.RequireClaim("role", "User", "Admin"));
});

// Rate limiting for auth endpoints
builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("auth", httpContext =>
    {
        var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        return RateLimitPartition.GetFixedWindowLimiter(ip, _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 10, // 每 60s 允许 10 次
            Window = TimeSpan.FromSeconds(60),
            QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
            QueueLimit = 0
        });
    });
});

var app = builder.Build();

 // Development OpenAPI
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

// Seed admin in development
using (var scope = app.Services.CreateScope())
{
    var sp = scope.ServiceProvider;
    var context = sp.GetRequiredService<IContext>();
    var configuration = sp.GetRequiredService<IConfiguration>();
    var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
    var logger = loggerFactory.CreateLogger("Seed");
    var env = sp.GetRequiredService<IHostEnvironment>();
    await SeedService.EnsureAdminAsync(context, configuration, logger, env);
}

// Static files for local storage
var storageRoot = builder.Configuration.GetSection("Storage")["Root"] ?? "./storage";
Directory.CreateDirectory(storageRoot);
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.GetFullPath(storageRoot)),
    RequestPath = "/static"
});

app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();
 app.MapGet("/ping", () => Results.Ok(new { ok = true }))
    .AllowAnonymous();
 
 // Log registered endpoints at startup
 var dataSource = app.Services.GetRequiredService<EndpointDataSource>();
 app.Logger.LogInformation("Endpoints: {endpoints}", string.Join(" | ", dataSource.Endpoints.Select(e => e.DisplayName)));

// Debug: list registered endpoints at startup
var eds = app.Services.GetRequiredService<EndpointDataSource>();
app.Logger.LogInformation("Registered endpoints: {list}", string.Join(" | ", eds.Endpoints.Select(e => e.DisplayName)));


//// Unified Minimal API mappings routed to service methods

static Microsoft.AspNetCore.Http.IResult ToResult<T>(Microsoft.AspNetCore.Mvc.ActionResult<T> ar)
{
    var result = ar.Result;
    if (result is Microsoft.AspNetCore.Http.IResult ir) return ir;
    if (result is Microsoft.AspNetCore.Mvc.ObjectResult o)
        return Microsoft.AspNetCore.Http.Results.Json(o.Value, statusCode: o.StatusCode ?? 200);
    if (result is Microsoft.AspNetCore.Mvc.StatusCodeResult sc)
        return Microsoft.AspNetCore.Http.Results.StatusCode(sc.StatusCode);
    if (result is Microsoft.AspNetCore.Mvc.ContentResult cr)
        return Microsoft.AspNetCore.Http.Results.Content(cr.Content ?? string.Empty, cr.ContentType ?? "text/plain", Encoding.UTF8);
    if (result is Microsoft.AspNetCore.Mvc.EmptyResult)
        return Microsoft.AspNetCore.Http.Results.NoContent();

    return Microsoft.AspNetCore.Http.Results.Ok(ar.Value);
}

var api = app.MapGroup("/api");

// ========== AUTH ==========
var auth = api.MapGroup("/auth");

// GET /api/auth/captcha
auth.MapGet("/captcha", (IMemoryCache cache, IConfiguration configuration) =>
{
    var svc = new HamsterDocs.Hosting.Services.CaptchaService();
    var r = svc.GetCaptcha(cache, configuration);
    return ToResult(r);
})
.AllowAnonymous()
.RequireRateLimiting("auth");

// POST /api/auth/login
auth.MapPost("/login", ([Microsoft.AspNetCore.Mvc.FromBody] HamsterDocs.Hosting.Services.AuthService.LoginRequest req,
                        IMemoryCache cache,
                        IConfiguration configuration,
                        IContext context) =>
{
    var svc = new HamsterDocs.Hosting.Services.AuthService();
    var r = svc.Login(req, cache, configuration, context);
    return ToResult(r);
})
.AllowAnonymous()
.RequireRateLimiting("auth");

// GET /api/auth/me
auth.MapGet("/me", (IContext context, ClaimsPrincipal principal) =>
{
    var svc = new HamsterDocs.Hosting.Services.AuthService();
    var r = svc.Me(context, principal);
    return ToResult(r);
})
.RequireAuthorization();

// GET /api/auth/oauth/providers
auth.MapGet("/oauth/providers", (IConfiguration configuration) =>
{
    var svc = new HamsterDocs.Hosting.Services.AuthService();
    var r = svc.GetProviders(configuration);
    return ToResult(r);
})
.AllowAnonymous();

// GET /api/auth/oauth/github/start
auth.MapGet("/oauth/github/start", (IConfiguration configuration) =>
{
    var svc = new HamsterDocs.Hosting.Services.AuthService();
    var r = svc.GithubStart(configuration);
    return ToResult(r);
})
.AllowAnonymous();

// POST /api/auth/oauth/github/exchange
auth.MapPost("/oauth/github/exchange", async ([Microsoft.AspNetCore.Mvc.FromBody] HamsterDocs.Hosting.Services.AuthService.OAuthExchangeRequest req,
                                              IConfiguration configuration,
                                              IContext context,
                                              IHttpClientFactory httpClientFactory) =>
{
    var svc = new HamsterDocs.Hosting.Services.AuthService();
    var r = await svc.GithubExchange(req, configuration, context, httpClientFactory);
    return ToResult(r);
})
.AllowAnonymous();

// GET /api/auth/oauth/gitee/start
auth.MapGet("/oauth/gitee/start", (IConfiguration configuration) =>
{
    var svc = new HamsterDocs.Hosting.Services.AuthService();
    var r = svc.GiteeStart(configuration);
    return ToResult(r);
})
.AllowAnonymous();

// POST /api/auth/oauth/gitee/exchange
auth.MapPost("/oauth/gitee/exchange", async ([Microsoft.AspNetCore.Mvc.FromBody] HamsterDocs.Hosting.Services.AuthService.OAuthExchangeRequest req,
                                             IConfiguration configuration,
                                             IContext context,
                                             IHttpClientFactory httpClientFactory) =>
{
    var svc = new HamsterDocs.Hosting.Services.AuthService();
    var r = await svc.GiteeExchange(req, configuration, context, httpClientFactory);
    return ToResult(r);
})
.AllowAnonymous();


// ========== USERS ==========
var users = api.MapGroup("/users");

users.MapPut("/profile", ([Microsoft.AspNetCore.Mvc.FromBody] HamsterDocs.Hosting.Services.UsersService.ProfileRequest req,
                          IContext context,
                          IHttpContextAccessor accessor) =>
{
    var svc = new HamsterDocs.Hosting.Services.UsersService();
    var r = svc.UpdateProfile(req, context, accessor);
    return ToResult(r);
})
.RequireAuthorization("User");

users.MapPut("/avatar", async (Microsoft.AspNetCore.Http.IFormFile file,
                               IConfiguration configuration,
                               IContext context,
                               ImageService imageService,
                               IHttpContextAccessor accessor) =>
{
    var svc = new HamsterDocs.Hosting.Services.UsersService();
    var r = await svc.UpdateAvatar(file, configuration, context, imageService, accessor);
    return ToResult(r);
})
.RequireAuthorization("User");


// ========== DOCS ==========
var docs = api.MapGroup("/docs");

docs.MapPost("/projects", async ([Microsoft.AspNetCore.Mvc.FromBody] HamsterDocs.Hosting.Services.DocsService.CreateProjectRequest req,
                                 IContext context,
                                 IHttpContextAccessor accessor) =>
{
    var svc = new HamsterDocs.Hosting.Services.DocsService();
    var r = await svc.CreateProject(req, context, accessor);
    return ToResult(r);
})
.RequireAuthorization("User");

docs.MapGet("/projects", async (IContext context,
                                IHttpContextAccessor accessor) =>
{
    var svc = new HamsterDocs.Hosting.Services.DocsService();
    var r = await svc.ListProjects(context, accessor);
    return ToResult(r);
})
.RequireAuthorization("User");

docs.MapPost("/trees", async ([Microsoft.AspNetCore.Mvc.FromBody] HamsterDocs.Hosting.Services.DocsService.CreateTreeRequest req,
                              IContext context,
                              IHttpContextAccessor accessor) =>
{
    var svc = new HamsterDocs.Hosting.Services.DocsService();
    var r = await svc.CreateTree(req, context, accessor);
    return ToResult(r);
})
.RequireAuthorization("User");

docs.MapGet("/trees", async ([Microsoft.AspNetCore.Mvc.FromQuery] Guid projectId,
                             IContext context,
                             IHttpContextAccessor accessor) =>
{
    var svc = new HamsterDocs.Hosting.Services.DocsService();
    var r = await svc.ListTrees(projectId, context, accessor);
    return ToResult(r);
})
.RequireAuthorization("User");

docs.MapGet("/items/{treeId}", async ([Microsoft.AspNetCore.Mvc.FromRoute] string treeId,
                                      IContext context,
                                      IHttpContextAccessor accessor) =>
{
    var svc = new HamsterDocs.Hosting.Services.DocsService();
    var r = await svc.GetItem(treeId, context, accessor);
    return ToResult(r);
})
.RequireAuthorization("User");

docs.MapPut("/items/{treeId}", async ([Microsoft.AspNetCore.Mvc.FromRoute] string treeId,
                                      [Microsoft.AspNetCore.Mvc.FromBody] HamsterDocs.Hosting.Services.DocsService.UpdateItemRequest req,
                                      IContext context,
                                      IHttpContextAccessor accessor) =>
{
    var svc = new HamsterDocs.Hosting.Services.DocsService();
    var r = await svc.UpdateItem(treeId, req, context, accessor);
    return ToResult(r);
})
.RequireAuthorization("User");

app.Run();